我們今天要來把前幾天寫的管理分類和管理地點與側邊欄結合,並且對 Item 模型進行修改,讓它能與分類和地點進行關聯。這樣,使用者在新增或修改物品時,能夠指定物品的分類和地點。在首頁查看物品時,也可以顯示該物品的分類圖示與地點,讓管理更加直覺、方便。
今天預計要實作以下功能:
準備好了嗎?開始囉~
我們在 Day14 實作側邊欄的時候,為了要快速顯示所有的側邊欄按鈕,所以使用一個自定義結構 MenuItem 來儲存側邊欄的功能,並且使用 ForEach
和 Button
來產生每一顆按鈕。
ForEach(items) { item in
HStack {
Button(action: {
}, label: {
Image(systemName: item.icon)
Text(item.text)
})
.foregroundColor(Color("FontColor"))
.frame(maxWidth: .infinity)
.font(.headline)
.padding()
.overlay {
RoundedRectangle(cornerRadius: 15)
.stroke(.gray, lineWidth: 2)
}
Spacer()
}
.padding(8)
}
現在我們來改寫這段程式碼,有兩種方法可以處理側邊欄按鈕的顯示:
原本的 MenuItem 結構只有 id、text 和 icon,現在我們加入 destination 來指定按鈕跳轉的頁面。
struct MenuItem: Identifiable {
var id = UUID()
let text: String
let icon: String
let destination: AnyView
}
AnyView 概念
AnyView 是 SwiftUI 中用來封裝不同型別 View 的一種工具。當我們需要在同一個結構內處理多種不同型別的 View,或動態生成 View 時,AnyView 允許將這些不同的 View 封裝成統一的型別來處理。由於 SwiftUI 的型別系統要求每個 View 必須有明確的型別,AnyView 提供了靈活性來處理動態或條件性顯示的 View。
使用 AnyView 可能會帶來一些效能損失,因為它隱藏了具體的型別資訊。為了最佳化效能,應該盡量在只有需要動態處理多型別視圖時使用 AnyView,不能將它作為預設解決方案。
參考資料:
透過改寫 MenuItem,我們可以繼續使用 ForEach
動態生成按鈕,避免重複的程式碼。
var body: some View {
ZStack {
Color("BgColor")
VStack(alignment: .leading, spacing: 0) {
ForEach(items) { item in
HStack {
NavigationLink(destination: item.destination) {
HStack {
Image(systemName: item.icon)
Text(item.text)
}
.foregroundColor(Color("FontColor"))
.frame(maxWidth: .infinity)
.font(.headline)
.padding()
.overlay {
RoundedRectangle(cornerRadius: 15)
.stroke(.gray, lineWidth: 2)
}
}
Spacer()
}
.padding(8)
}
Spacer()
}
}
}
這種方法適合所有按鈕都需要跳轉頁面的情況。不過,像「給予評分」和「聯絡我們」這些按鈕通常不會跳轉頁面,這時我們可以採用第二種方法。
捨棄 MenuItem 結構,以手動的方式建立按鈕,為了避免重複性程式碼太多,我把可以元件化的程式抽出來處理,因此我們刪除 MenuItem,並建立新的 MenuButton。
為了避免重複性程式碼,我們將按鈕樣式抽離成元件,這樣可以簡化程式碼。
struct MenuButton: View {
let title: String
let icon: String
var body: some View {
HStack {
Image(systemName: icon)
Text(title)
}
.foregroundColor(Color("FontColor"))
.frame(maxWidth: .infinity)
.font(.headline)
.padding()
.overlay {
RoundedRectangle(cornerRadius: 15)
.stroke(.gray, lineWidth: 2)
}
}
}
移除掉原本的程式碼,改用一個一個按鈕手動建立的方式,在中間需要按鈕樣式的地方呼叫剛剛寫好的 MenuButton,這樣可以手動控制每個按鈕的行為。
var body: some View {
ZStack {
Color("BgColor")
VStack(alignment: .leading, spacing: 0) {
NavigationLink(destination: CategoryListView()) {
MenuButton(title: "管理分類", icon: "square.grid.2x2")
}
.padding(8)
NavigationLink(destination: LocationListView()) {
MenuButton(title: "管理地點", icon: "location")
}
.padding(8)
NavigationLink(destination: LocationListView()) {
MenuButton(title: "帳務報表", icon: "chart.pie")
} // 因為還沒建立好報表的頁面,先用 LocationListView 代替一下
.padding(8)
Button(action: {
}, label: {
MenuButton(title: "給予評分", icon: "star.bubble")
})
.padding(8)
Button(action: {
}, label: {
MenuButton(title: "與我聯絡", icon: "envelope")
})
.padding(8)
Spacer()
}
}
}
接下來,我們對 Item 模型進行修改,讓每個物品都能關聯到一個分類和一個地點。這樣使用者在新增或修改物品時,可以選擇物品的分類和存放地點,讓物品管理更加清晰。
平常我們都是在修改 Attributes 欄位,現在我們需要在 Relationships 新增 category 關聯 ItemCategory、location 關聯 Location。
別忘了更新程式碼:
extension Item {
@nonobjc public class func fetchRequest() -> NSFetchRequest<Item> {
return NSFetchRequest<Item>(entityName: "Item")
}
@NSManaged public var dateAdded: Date?
@NSManaged public var expiryDate: Date?
@NSManaged public var id: UUID?
@NSManaged public var isUsedUp: Bool
@NSManaged public var name: String
@NSManaged public var price: Double
@NSManaged public var quantity: Int16
@NSManaged public var usedQuantity: Int16
@NSManaged public var category: ItemCategory
@NSManaged public var location: Location
}
我們新增了 category 和 location 屬性,用來儲存物品所屬的分類和存放地點。每當使用者在新增或修改物品時,可以選擇這兩個屬性。
今天我們將分類與地點管理功能整合到側邊欄,並且更新了 Item 模型,讓物品能夠與分類和地點進行關聯。這些改進將為接下來的功能實作打下堅實的基礎。明天,我們將優化新增與編輯物品的頁面,並將分類和地點選項整合進去。明天見!